package com.dly.hope.servce.impl;

import com.dly.hope.repository.*;
import com.dly.hope.servce.WorkflowService;
import com.dly.hope.utils.JsonFormatUtil;
import com.dly.hope.workflow.*;
import com.dly.hope.workflow.dto.ApprRecordDTO;
import com.dly.hope.workflow.dto.WorkflowData;
import com.dly.hope.workflow.ro.ConditionInfoRO;
import com.dly.hope.workflow.ro.NodeInfoRO;
import com.dly.hope.workflow.ro.NodeRolesRO;
import com.dly.hope.workflow.ro.ProcessInfoRO;
import com.dly.hope.workflow.type.ApprStatus;
import com.dly.hope.workflow.type.DataStatusEnum;
import com.dly.hope.workflow.type.FlagEnum;
import com.dly.hope.workflow.type.ProcessEnum;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.validation.constraints.NotNull;
import java.util.*;
import java.util.function.Consumer;

/**
 * @author DBC20220821
 */
@Service
public class WorkflowServiceImpl implements WorkflowService {

    @Resource
    private ProcessInfoRepository processInfoRepository;

    @Resource
    private NodeInfoRepository nodeInfoRepository;

    @Resource
    private NodeRoleRepository nodeRoleRepository;

    @Resource
    private ConditionInfoRepository conditionInfoRepository;

    @Resource
    private ProcessInstanceRepository processInstanceRepository;

    @Resource
    private NodeInstanceRepository nodeInstanceRepository;

//    @Resource
//    private UserInfoRepository userInfoRepository;

//    @Resource
//    private ExecuteRequestService executeRequestService;
//
//    @Value("${erpng.appr-statistics.server:}")
//    private String apprStatisticsServer;

    /**
     * 数据状态：有效
     */
    private static final Integer ACTIVE_STATUS = DataStatusEnum.effective.getCode();

    /**
     * 条件表达式中上一个审批节点的变量名
     */
    private static final String PRE_NODE = "node";

    /**
     * 条件表达式中创建过的节点的变量名
     */
    public static final String CREATED_NODE = "createdNode";

    /**
     * 条件表达式中上一个审批节点审批结果的变量名
     */
    public static final String PRE_NODE_APPR_RESULT = "result";

    /**
     * 条件表达式中表示审批通过的值
     */
    private static final String APPR_SUCCESS = "Y";

    /**
     * 条件表达式中表示审批拒绝/驳回的值
     */
    private static final String APPR_REJECT = "N";

    /**
     * 发起审批的标识符
     */
    private static final String START = "START";

    /**
     * 发起审批的节点定义ID
     */
    private static final String START_NODE_ID = "0";

    /**
     * 初试审批节点
     */
    private static final String NODE_ROOT = "root";


    /**
     * @Description: 根据流程实例ID查询审批履历
     * @Param: [processInstanceId]
     * @return: java.util.List<com.dragonbravo.magicfund.model.dto.ApprRecordDTO>
     * @Author: Danny Chen
     * @Date: 2022/1/23
     */
    @Override
    public List<ApprRecordDTO> getApprRecords(@NotNull String processInstanceId) {
//        return nodeInstanceRepository.getApprRecordDtoByProcesInstanceId(processInstanceId, ACTIVE_STATUS, FlagEnum.NO.getCode(), FlagEnum.YES.getCode());
        return null;
    }

    /**
     * @Description: 根据节点实例ID查询审批履历 (某些业务表没有存流程实例ID可以用此方法)
     * @Param: [processInstanceId]
     * @return: java.util.List<com.dragonbravo.magicfund.model.dto.ApprRecordDTO>
     * @Author: Danny Chen
     * @Date: 2022/1/23
     */
    @Override
    public List<ApprRecordDTO> getApprRecordsByNodeInstanceId(String nodeInstanceId) {
        if (StringUtils.isBlank(nodeInstanceId)) {
            return Lists.newArrayList();
        }
        NodeInstance nodeInstance = nodeInstanceRepository.findById(nodeInstanceId).orElseThrow(() -> new RuntimeException("NodeInstance is not found for id: " + nodeInstanceId));
        return this.getApprRecords(nodeInstance.getProcessInstanceId());
    }

//    /**
//     * @Description: 用户登录ERP的时候，根据角色查询所有待审批的条据统计
//     * @Param: [roleId]
//     * @return: java.util.List<com.dragonbravo.magicfund.model.dto.ApprCountDTO>
//     * @Author: Danny Chen
//     * @Date: 2022/1/23
//     */
//    @Override
//    public List<ApprCountDTO> getPendingNodesByRoleId(@NotNull String userId, @NotNull String roleId) {
//        List<ApprCountDTO> statisticsList = nodeInstanceRepository.getCountByRoleId(roleId, ACTIVE_STATUS, FlagEnum.NO.getCode());
//        if (CollectionUtils.isEmpty(statisticsList)) {
//            return statisticsList;
//        }
//
//        Map<String, ProcessInfo> processMap = processInfoRepository
//                .findByNameInAndDataStatus(statisticsList.stream().map(ApprCountDTO::getProcessName).collect(Collectors.toSet()), ACTIVE_STATUS)
//                .stream().collect(Collectors.toMap(ProcessInfo::getName, Function.identity(), (prc1, prc2) -> prc1));
//
//        List<ApprCountDTO> resultList = Lists.newArrayList();
//        statisticsList.forEach(stc -> {
//            ProcessInfo processInfo = processMap.get(stc.getProcessName());
//            if (Objects.nonNull(processInfo)) {
//                stc.setCount(getCountByUserIdAndRoleId(processInfo.getStatisticApi(), userId, roleId, stc.getCount()));
//                if (stc.getCount() > 0L) {
//                    resultList.add(stc);
//                }
//            }
//        });
//        return resultList;
//    }

//    /**
//     * @Description: 传入userId, roleId, 调用待审批件API获取统计数量
//     * @Param: [api, userId, roleId]
//     * @return: java.lang.Long
//     * @Author: Danny Chen
//     * @Date: 2022/9/28
//     */
//    private Long getCountByUserIdAndRoleId(String api, String userId, String roleId, Long defaultCount) {
//        if (StringUtils.isBlank(api)) {
//            return defaultCount;
//        }
//        if (!StringUtils.startsWith(api, "http")) {
//            if (StringUtils.startsWith(api, "/")) {
//                api = StringUtils.substring(api, NumericConstant.ONE);
//            }
//            api = apprStatisticsServer + StringUtils.replace(api, "{userId}", userId).replace("{roleId}", roleId);
//        }
//        //Opr<Long> result = executeRequestService.sendRequest(api, null, HttpMethod.POST, null, Opr.class, Long.class);
//        try {
//            Map result = executeRequestService.sendGetRequest(api, Map.class, null);
//            String count = String.valueOf(result.get("data"));
//            if (!"null".equalsIgnoreCase(count)) {
//                return Long.valueOf(count);
//            }
//        } catch (Exception e) {
//            PssLogFactory.getErrorLog().error("getCountByUserIdAndRoleId failed for API: " + api, e);
//        }
//        return 0L;
//    }


    /**
     * @Description: 发起流程实例，当不满足任何审批条件时会抛出RuntimeException
     * @Param: [processEnum, sponsorId, expireDays, context, callback]
     * @return: void
     * @Author: Danny Chen
     * @Date: 2022/1/19
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void createProcessInstance(ProcessEnum processEnum,
                                      String sponsorId,
                                      Integer expireDays,
                                      Map<String, Object> context,
                                      Consumer<WorkflowData> callback) {
        //查找审批
        ProcessInfo processInfo = Optional.ofNullable(processInfoRepository.findByNameAndDataStatus(processEnum.getName(), ACTIVE_STATUS)).orElseThrow(() -> new RuntimeException("ProcessInfo is not found for name: " + processEnum.getName()));
        Date expireDate = Objects.isNull(expireDays) ? null : DateUtils.addDays(new Date(), expireDays);

        this.createProcessInstance(processInfo.getId(), sponsorId, expireDate, context, callback);
    }

    /**
     * @Description: 处理审批（当操作员点击审批通过或驳回的时候需要调用此方法）：
     * 更新并关闭当前节点实例，创建下一个节点实例或者终结流程
     * @Param: [nodeId, approverId, apprResult, suggestion, callback]
     * @return: void
     * @Author: Danny Chen
     * @Date: 2022/1/19
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void approval(@NotNull String nodeInstanceId,
                         String approverId,
                         String apprResult,
                         String suggestion,
                         String remark,
                         Consumer<WorkflowData> callback) {

        NodeInstance nodeInstance = nodeInstanceRepository.findById(nodeInstanceId).orElseThrow(() -> new RuntimeException("NodeInstance is not found for id: " + nodeInstanceId));

        if (FlagEnum.YES.getCode().equals(nodeInstance.getCompleteFlag())) {
            throw new RuntimeException("The NodeInstance has been approved for given id: " + nodeInstanceId);
        }

        /*List<String> roleIds = nodeRoleRepository.getRoleIdByNodeIdAndDataStatus(nodeInstance.getNodeId(), ACTIVE_STATUS);
        UserInfo approver = userInfoRepository.findById(approverId).orElseThrow(() -> new RuntimeException("Approver info is not found, id: " + approverId));
        if (!roleIds.contains(approver.getRoleId())) {
            throw new RuntimeException("The user's role is not in the Approval Roles of this node, user id: " + approverId);
        }*/

        NodeInfo nodeInfo = nodeInfoRepository.findById(nodeInstance.getNodeId()).orElseThrow(() -> new RuntimeException("NodeInfo is not found for id: " + nodeInstance.getNodeId()));
        ProcessInstance processInstance = processInstanceRepository.findById(nodeInstance.getProcessInstanceId()).orElseThrow(() -> new RuntimeException("ProcessInstance is not found for id: " + nodeInstance.getProcessInstanceId()));

        Map<String, Object> context = StringUtils.isBlank(nodeInstance.getData()) ? Maps.newHashMap() : JsonFormatUtil.readFromJsonString(nodeInstance.getData(), Map.class);

        context.put(PRE_NODE, nodeInfo.getName());
        context.put(PRE_NODE_APPR_RESULT, apprResult);

        NodeInstance currentNode = this.createNodeInstanceByContext(processInstance, context);

        this.completeNodeInstance(nodeInstanceId, currentNode.getId(), approverId, apprResult, suggestion, remark);

        //回调函数传入当前流程实例ID、节点实例ID、审批状态，业务方可做相应处理
        callback.accept(new WorkflowData(processInstance.getId(), currentNode.getId(), processInstance.getApprStatus()));

    }


    /**
     * @Description: 重新发起审批流程，当单据被拒绝且发起人修改后重新提交时需要调用此方法重新发起流程
     * @Param: [processInstanceId, context, sponsorUserId, callback]
     * @return: void
     * @Author: Danny Chen
     * @Date: 2022/1/20
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void restartProcessInstance(@NotNull String processInstanceId,
                                       String sponsorId,
                                       Map<String, Object> context,
                                       Consumer<WorkflowData> callback) {
        ProcessInstance processInstance = processInstanceRepository.findById(processInstanceId).orElseThrow(() -> new RuntimeException("ProcessInstance is not found for id: " + processInstanceId));

        //修改之前的节点实例activeFlag
        List<NodeInstance> previousNodes = nodeInstanceRepository.findAllByProcessInstanceIdInAndDataStatus(List.of(processInstanceId), 1);
        previousNodes.forEach(nodeInstance -> nodeInstance.setActiveFlag(FlagEnum.NO.getCode()));

        //在审批履历中添加一条发起人记录
        this.createSponsorNodeInstance(processInstance, sponsorId);

        //找出该审批流的最后一个节点实例
        NodeInstance lastNode = nodeInstanceRepository.findById(processInstance.getCurrentNodeId()).orElseThrow(() -> new RuntimeException("NodeInstance is not found for id: " + processInstance.getCurrentNodeId()));

        //context = Objects.isNull(context) ? Maps.newHashMap() : context;
        context = Optional.ofNullable(context).orElse(Maps.newHashMap());

        context.put(PRE_NODE, NODE_ROOT);

        //基于当前业务数据计算出唯一的条件，并根据条件指向的节点创建节点实例
        NodeInstance currentNode = this.createNodeInstanceByContext(processInstance, context);

        //将最后这个节点实例的nextNode指向这个新的节点实例
        lastNode.setNextNodeId(currentNode.getId());

        //回调函数将当前流程实例ID、节点实例ID、审批状态更新到formId对应的表单数据
        callback.accept(new WorkflowData(processInstance.getId(), currentNode.getId(), processInstance.getApprStatus()));
    }

    /**
     * @Description:
     * @Param: [processId, sponsorId, expireDate, context, callback]
     * @return: void
     * @Author: Danny Chen
     * @Date: 2022/1/20
     */
    private void  createProcessInstance(String processId, String sponsorId, Date expireDate, Map<String, Object> context, Consumer<WorkflowData> callback) {
        ProcessInstance processInstance = new ProcessInstance();
        processInstance.setProcessId(processId);
        processInstance.setSponsorUserId(sponsorId);
        processInstance.setExpireDate(expireDate);
        //processInstance.setFormId(formId);

        processInstanceRepository.save(processInstance);

        //在审批履历中添加一条发起人记录
        this.createSponsorNodeInstance(processInstance, sponsorId);

        //context = Objects.isNull(context) ? Maps.newHashMap() : context;
        context = Optional.ofNullable(context).orElse(Maps.newHashMap());

        context.put(PRE_NODE, NODE_ROOT);
        context.put(CREATED_NODE, Lists.newArrayList(NODE_ROOT));

        //基于当前业务数据计算出唯一的条件，并根据条件指向的节点创建节点实例
        NodeInstance currentNode = this.createNodeInstanceByContext(processInstance, context);

        //将此节点实例作为开始节点设置到流程实例中
        processInstance.setStartNodeId(currentNode.getId());

        //回调函数将当前流程实例ID、节点实例ID、审批状态更新到formId对应的表单数据
        callback.accept(new WorkflowData(processInstance.getId(), currentNode.getId(), processInstance.getApprStatus()));
    }



    /**
     * @Description: 创建发起人节点（虚拟开始节点）
     * 业务场景：
     * 在每次新建、或驳回后重新发起审批流程的时候，都需要记录最新的发起人（提交人），显示在审批履历中，
     * 因此每次新建或重新发起流程的时候调用下面这个方法可以添加一个虚拟的节点信息记录发起人，显示在履历中。
     * @Param: [processInstanceId]
     * @return: com.dragonbravo.magicfund.model.workflow.NodeInstance
     * @Author: Danny Chen
     * @Date: 2022/6/16
     */
    private NodeInstance createSponsorNodeInstance(ProcessInstance processInstance, String sponsorUserId) {
        //currentDisplayNumber不为 0 说明是重新发起审批，number + 1
        if (processInstance.getCurrentDisplayNo() != 0) {
            processInstance.setCurrentDisplayNo(processInstance.getCurrentDisplayNo() + 1);
        }
        NodeInstance sponsor = new NodeInstance();
        sponsor.setNodeId(START_NODE_ID);
        sponsor.setProcessInstanceId(processInstance.getId());
        sponsor.setDisplayNo(processInstance.getCurrentDisplayNo());
        sponsor.setCompleteFlag(FlagEnum.YES.getCode());
        sponsor.setApprDate(new Date());
        sponsor.setApproverId(sponsorUserId);
        sponsor.setApprResult(START);
        nodeInstanceRepository.save(sponsor);
        return sponsor;
    }

    /**
     * @Description: 创建节点实例 - 基于业务数据和条件查找节点
     * @Param: [processId, processInstance, context]
     * @return: com.dragonbravo.magicfund.model.workflow.NodeInstance
     * @Author: Danny Chen
     * @Date: 2022/1/19
     */
    private NodeInstance createNodeInstanceByContext(ProcessInstance processInstance, Map<String, Object> context) {
        List<ConditionInfo> conditions = conditionInfoRepository.findByProcessIdAndDataStatusOrderBySeqNoAsc(processInstance.getProcessId(), ACTIVE_STATUS);
        ExpressionParser parser = new SpelExpressionParser();
        StandardEvaluationContext evalContext = new StandardEvaluationContext();
        evalContext.setVariables(context);

        for (ConditionInfo condition : conditions) {
            Expression exp = parser.parseExpression(condition.getExpression());
            /*根据context判断是否符合创建节点的条件, 如果被驳回了就会创建虚拟中止节点*/
            if (exp.getValue(evalContext, Boolean.class)) {

                //创建新节点实例
                NodeInfo nodeInfo = nodeInfoRepository.findById(condition.getGotoNodeId()).orElseThrow(() -> new RuntimeException("NodeInfo is not found for id: " + condition.getGotoNodeId()));
                NodeInstance currentNode = this.createNodeInstance(nodeInfo, processInstance, context);
                processInstance.setCurrentNodeId(currentNode.getId());

                //如果是最后一个节点（虚拟终止节点），需进行如下处理：
                if (FlagEnum.YES.getCode().equals(nodeInfo.getEndNodeFlag())) {
                    //判断最近一次审批结果，如果result=Y表示整个流程审批通过，其他表示拒绝
                    if (APPR_SUCCESS.equalsIgnoreCase(String.valueOf(context.get(PRE_NODE_APPR_RESULT)))) {
                        processInstance.setApprStatus(ApprStatus.SUCCESS.getStatus());
                        currentNode.setApprResult(APPR_SUCCESS);
                    } else {
                        processInstance.setApprStatus(ApprStatus.REJECT.getStatus());
                        currentNode.setApprResult(APPR_REJECT);
                    }
                    //关闭这个虚拟终止节点
                    this.completeNodeInstance(currentNode.getId(), null, null, currentNode.getApprResult(), null, null);
                } else {
                    processInstance.setApprStatus(ApprStatus.IN_PROGRESS.getStatus());
                    //如果不是最后一个节点，则发送消息通知给节点对应的审批角色的员工
                    this.sendApprNotification(condition.getGotoNodeId());
                }

                processInstanceRepository.save(processInstance);

                return currentNode;
            }
        }
        throw new RuntimeException("Cannot found any mapped condition");
    }

    /**
     * @Description: 创建新节点实例
     * @Param: [nodeInfo, processInstance, context]
     * @return: java.lang.String
     * @Author: Danny Chen
     * @Date: 2022/1/19
     */
    private NodeInstance createNodeInstance(NodeInfo nodeInfo, ProcessInstance processInstance, Map<String, Object> context) {
        processInstance.setCurrentDisplayNo(processInstance.getCurrentDisplayNo() + 1);
        NodeInstance nodeInstance = new NodeInstance();
        nodeInstance.setNodeId(nodeInfo.getId());
        nodeInstance.setProcessInstanceId(processInstance.getId());
        nodeInstance.setDisplayNo(processInstance.getCurrentDisplayNo());

        /*把创建过的节点名称加入 context*/
        List<String> createdNode = CollectionUtils.isEmpty((List<String>) context.get(CREATED_NODE)) ? Lists.newArrayList() : (List<String>) context.get(CREATED_NODE);
        createdNode.add(nodeInfo.getName());
        context.put(CREATED_NODE, createdNode);

        nodeInstance.setData(JsonFormatUtil.writeToJson(context));
        nodeInstanceRepository.save(nodeInstance);

        return nodeInstance;
    }

    /**
     * @Description: 完成（关闭）节点实例
     * @Param: [nodeId, nextNodeId, approverId, apprResult, suggestion, remark]
     * @return: void
     * @Author: Danny Chen
     * @Date: 2022/1/19
     */
    private void completeNodeInstance(String nodeId, String nextNodeId, String approverId, String apprResult, String suggestion, String remark) {
        NodeInstance nodeInstance = nodeInstanceRepository.findById(nodeId).orElseThrow(() -> new RuntimeException("NodeInstance is not found for id: " + nodeId));
        nodeInstance.setCompleteFlag(FlagEnum.YES.getCode());
        nodeInstance.setApprDate(new Date());
        nodeInstance.setNextNodeId(nextNodeId);
        nodeInstance.setApproverId(approverId);
        nodeInstance.setApprResult(apprResult);
        nodeInstance.setSuggestion(suggestion);
        nodeInstance.setRemark(remark);
        nodeInstanceRepository.save(nodeInstance);
    }

    /**
     * @Description: 消息通知节点对应的审批角色的员工
     * @Param: [nodeId]
     * @return: void
     * @Author: Danny Chen
     * @Date: 2022/1/19
     */
    private void sendApprNotification(String nodeId) {
        //TODO 消息通知审批角色
        /*List<String> roleIds = nodeRoleRepository.getRoleIdByNodeIdAndDataStatus(nodeId, DataStatusEnum.effective.getCode());
        List<UserInfo> userInfos = userInfoRepository.findByRoleIdInAndDataStatus(roleIds, DataStatusEnum.effective.getCode());
        userInfos.forEach(user -> {
            mailService.workflowNotificationMail(user.getEmpInfo().getEmail());
        });*/
    }


    @Transactional(rollbackFor = Exception.class)
    @Override
    public ProcessInfo saveProcessInfo(ProcessInfoRO processInfoRO) {
        ProcessInfo processInfo = new ProcessInfo();
        BeanUtils.copyProperties(processInfoRO, processInfo);
        return processInfoRepository.save(processInfo);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public NodeInfo saveNodeInfo(NodeInfoRO nodeInfoRO) {
        NodeInfo nodeInfo = new NodeInfo();
        BeanUtils.copyProperties(nodeInfoRO, nodeInfo);
        return nodeInfoRepository.save(nodeInfo);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public ConditionInfo saveConditionInfo(ConditionInfoRO conditionInfoRO) {
        ConditionInfo conditionInfo = new ConditionInfo();
        BeanUtils.copyProperties(conditionInfoRO, conditionInfo);
        return conditionInfoRepository.save(conditionInfo);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void saveNodeRoles(NodeRolesRO nodeRolesRO) {
        if (CollectionUtils.isEmpty(nodeRolesRO.getRoleIds())) {
            throw new RuntimeException("roleIds cannot be null");
        }
        List<NodeRole> nodeRoles = Lists.newArrayList();
        nodeRolesRO.getRoleIds().forEach(roleId -> {
            NodeRole nr = new NodeRole();
            nr.setNodeId(nodeRolesRO.getNodeId());
            nr.setRoleId(roleId);
            nodeRoles.add(nr);
        });
        nodeRoleRepository.saveAll(nodeRoles);
    }

    /**
     * @author: yonghua.huang
     * @description: 为了兼容售前检测奇奇怪怪的问题所重载的方法，不生成提交记录
     * @param: [processInstanceId, sponsorId, context, callback]
     * @return: void
     * @date 2022/7/1 10:34
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void restartProcessInstanceWithNoStart(@NotNull String processInstanceId,
                                                  String sponsorId,
                                                  Map<String, Object> context,
                                                  Consumer<WorkflowData> callback) {
        ProcessInstance processInstance = processInstanceRepository.findById(processInstanceId).orElseThrow(() -> new RuntimeException("ProcessInstance is not found for id: " + processInstanceId));

        // 在审批履历中添加一条发起人记录
        // this.createSponsorNodeInstance(processInstance, sponsorId);

        //找出该审批流的最后一个节点实例
        NodeInstance lastNode = nodeInstanceRepository.findById(processInstance.getCurrentNodeId()).orElseThrow(() -> new RuntimeException("NodeInstance is not found for id: " + processInstance.getCurrentNodeId()));

        //context = Objects.isNull(context) ? Maps.newHashMap() : context;
        context = Optional.ofNullable(context).orElse(Maps.newHashMap());

        context.put(PRE_NODE, NODE_ROOT);

        //基于当前业务数据计算出唯一的条件，并根据条件指向的节点创建节点实例
        NodeInstance currentNode = this.createNodeInstanceByContext(processInstance, context);

        //将最后这个节点实例的nextNode指向这个新的节点实例
        lastNode.setNextNodeId(currentNode.getId());

        //回调函数将当前流程实例ID、节点实例ID、审批状态更新到formId对应的表单数据
        callback.accept(new WorkflowData(processInstance.getId(), currentNode.getId(), processInstance.getApprStatus()));
    }

    /**
     * @Description: 创建审批流实例, 为了兼容售前检测奇奇怪怪的问题所重载的方法，不生成提交记录
     * @Param: [processEnum, sponsorId, expireDays, context, callback]
     * @return: void
     * @Author: Danny Chen
     * @Date: 2022/1/19
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void createProcessInstanceWithNoStart(ProcessEnum processEnum,
                                                 String sponsorId,
                                                 Integer expireDays,
                                                 Map<String, Object> context,
                                                 Consumer<WorkflowData> callback) {

        ProcessInfo processInfo = Optional.ofNullable(processInfoRepository.findByNameAndDataStatus(processEnum.getName(), ACTIVE_STATUS)).orElseThrow(() -> new RuntimeException("ProcessInfo is not found for name: " + processEnum.getName()));
        Date expireDate = Objects.isNull(expireDays) ? null : DateUtils.addDays(new Date(), expireDays);

        ProcessInstance processInstance = new ProcessInstance();
        processInstance.setProcessId(processInfo.getId());
        processInstance.setSponsorUserId(sponsorId);
        processInstance.setExpireDate(expireDate);
        //processInstance.setFormId(formId);

        processInstanceRepository.save(processInstance);

        //在审批履历中添加一条发起人记录
        // this.createSponsorNodeInstance(processInstance, sponsorId);

        //context = Objects.isNull(context) ? Maps.newHashMap() : context;
        context = Optional.ofNullable(context).orElse(Maps.newHashMap());

        context.put(PRE_NODE, NODE_ROOT);
        context.put(CREATED_NODE, Lists.newArrayList(NODE_ROOT));

        //基于当前业务数据计算出唯一的条件，并根据条件指向的节点创建节点实例
        NodeInstance currentNode = this.createNodeInstanceByContext(processInstance, context);

        //将此节点实例作为开始节点设置到流程实例中
        processInstance.setStartNodeId(currentNode.getId());

        //回调函数将当前流程实例ID、节点实例ID、审批状态更新到formId对应的表单数据
        callback.accept(new WorkflowData(processInstance.getId(), currentNode.getId(), processInstance.getApprStatus()));
    }

    /**
     * @Description: 创建发起人节点（虚拟开始节点）
     * 业务场景：为了兼容售前奇奇怪怪的问题所以拎出来public
     * 在每次新建、或驳回后重新发起审批流程的时候，都需要记录最新的发起人（提交人），显示在审批履历中，
     * 因此每次新建或重新发起流程的时候调用下面这个方法可以添加一个虚拟的节点信息记录发起人，显示在履历中。
     * @Param: [processInstanceId]
     * @Author: Danny Chen
     * @Date: 2022/6/16
     */
    @Override
    public void createSponsorNodeInstanceWithPreSale(ProcessInstance processInstance, String sponsorUserId) {
        //currentDisplayNumber不为 0 说明是重新发起审批，number + 1
        if (processInstance.getCurrentDisplayNo() != 0) {
            processInstance.setCurrentDisplayNo(processInstance.getCurrentDisplayNo() + 1);
        }
        NodeInstance sponsor = new NodeInstance();
        sponsor.setNodeId(START_NODE_ID);
        sponsor.setProcessInstanceId(processInstance.getId());
        sponsor.setDisplayNo(processInstance.getCurrentDisplayNo());
        sponsor.setCompleteFlag(FlagEnum.YES.getCode());
        sponsor.setApprDate(new Date());
        sponsor.setApproverId(sponsorUserId);
        sponsor.setApprResult(START);
        nodeInstanceRepository.save(sponsor);
    }

}
